﻿Option Explicit On

Imports System.Xml
Imports System.Net
Imports System.Management

Public Class EnviroBuilder

#Region " CONSTANTS "

    Public Const AppName As String = "CdrEnviroBuilder"
    Public Const LogName As String = "Application"
    Public Const EnviroFileName As String = "Environment.xml"
    Public Const AwsInstanceMetaUrl As String = "http://169.254.169.254/latest/meta-data/"
    Public Const AwsInstanceMetaInstId As String = "instance-id/"


#End Region


#Region " SHARED VARIABLES "

    Shared Log As EventLog = Nothing
    Public Shared MustRestart As Boolean = False 'set to true if the machines should go for a restart after completing the environment

    ''' <summary>
    ''' Environment.xml
    ''' </summary>
    ''' <remarks></remarks>
    Shared xEnviro As XmlDocument = Nothing


    ''' <summary>
    ''' Instance description from AWS as XML
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared xInstace As XmlDocument = Nothing

    ''' <summary>
    ''' Instance description from AWS as object
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared oInstance As Amazon.EC2.Model.Reservation = Nothing

    ''' <summary>
    ''' List of all instances 
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared oInstances As Collections.Generic.List(Of Amazon.EC2.Model.Reservation)

    ''' <summary>
    ''' All instances from AWS as XML
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared xInstaces As XmlDocument = Nothing

    ''' <summary>
    ''' Number of logical disks available. Use it to track new additions.
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared iDisks As Integer = 0

    ''' <summary>
    ''' Number of physical disks available. Use it to track new additions.
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared iDisksPhys As Integer = 0

    ''' <summary>
    ''' List of disks known to the service
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared arDisksPhys As Collections.Generic.List(Of WmiDisk)


    Public Shared sInstanceID As String = Nothing 'set once after the service is started
    Public Shared sAwsKey As String = "" 'loaded and set with settings
    Public Shared sAwsKeySecret As String = "" 'loaded and set with settings


#End Region


#Region " TIMERS "

    Dim oTimer As Timers.Timer = Nothing

#End Region


    Protected Overrides Sub OnStart(ByVal args() As String)

        'initialise
        InitialiseLogging()
        LoadSettings()

        'sInstanceID = "i-0fdb8967"

        'give it a few secs and kick off environment building
        oTimer = New Timers.Timer(5000)
        oTimer.AutoReset = False
        AddHandler oTimer.Elapsed, AddressOf ReTryCreateEnviro
        oTimer.Start()
        'failed to initialise - try again later




    End Sub

    Protected Overrides Sub OnStop()
        If Not oTimer Is Nothing AndAlso oTimer.Enabled Then oTimer.Stop()
        ' Add code here to perform any tear-down necessary to stop your service.
    End Sub

    ''' <summary>
    ''' A timer event to try and re-build the environment. Restarts itsef on failure
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub ReTryCreateEnviro(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs)

        WriteToLog("ReTryCreateEnviro entered.", EventLogEntryType.Information)

        Threading.Thread.CurrentThread.Sleep(30000)

        ' sInstanceID = "i-f1ca9d99"

        If Not GetInstanceID() Then
            'try again later
            oTimer.Start()
            Return
        End If

        If Not GetInstaceDetails() Then
            'try again later
            oTimer.Start()
            Return
        End If

        'Return

        'get list of disks
        Dim xDisks As XmlNodeList = Nothing
        For Each xD As XmlElement In xEnviro.DocumentElement.ChildNodes
            If xD.LocalName = "disks" Then
                xDisks = xD.ChildNodes
                Exit For
            End If
        Next

        If xDisks.Count = 0 Then
            WriteToLog("No disks found in the config file", EventLogEntryType.Warning)
        ElseIf Not DisksRecursive(xDisks) Then
            WriteToLog("Environment NOT prepared. Will try again later.", EventLogEntryType.Information)
            oTimer.Start()
            Return
        End If


        WriteToLog("Environment prepared.", EventLogEntryType.Warning)

        'all done. Restart if needed.
        If MustRestart Then Restart() Else MyBase.Stop()

    End Sub


    ''' <summary>
    ''' Process folders one by one
    ''' </summary>
    ''' <param name="xFolder"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function FolderRecursive(ByRef xFolder As XmlElement) As Boolean

        'process the current folder
        If Not Folders.ProcessFolder(xFolder) Then Return False

        'do the recursion 
        For Each xF As XmlElement In xFolder.ChildNodes
            If xF.LocalName = "folder" AndAlso Not FolderRecursive(xF) Then Return False
        Next

        'all done
        Return True

    End Function


    ''' <summary>
    ''' Process disks one by one
    ''' </summary>
    ''' <param name="xDisks"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function DisksRecursive(ByRef xDisks As XmlNodeList) As Boolean

        'loop through all disks
        For Each xDisk As XmlElement In xDisks
            If Not Disks.Process(xDisk) Then Return False
        Next

        Dim sDisks As String = ""
        For Each sDisk As String In Environment.GetLogicalDrives
            sDisks += " " + sDisk + ","
        Next
        sDisks = sDisks.TrimEnd(","c)
        WriteToLog("Disks available: " + sDisks, EventLogEntryType.Warning)

        'process folders now
        For Each xDisk As XmlElement In xDisks
            'process all folders recursively until end or error
            Dim xFolders As XmlNodeList = xDisk.ChildNodes
            For Each xFolder As XmlElement In xFolders
                If Not FolderRecursive(xFolder) Then Return False
            Next
        Next


        'all done
        Return True


    End Function



    ''' <summary>
    ''' Restart this machine
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub Restart()

        'Return

        Try
            Dim sShut As String = "shutdown -r -f -t 05"
            WriteToLog("Restarting ... ", EventLogEntryType.Warning)
            'Shell(sShut, AppWinStyle.Hide, False)
        Catch ex As Exception
            WriteToLog("Failed to restart", EventLogEntryType.Error)
        End Try


    End Sub



#Region " LOGGING "

    ''' <summary>
    ''' Prepare error log
    ''' </summary>
    ''' <remarks></remarks>
    Private Shared Sub InitialiseLogging()

        'do we have the source registered?
        If Not EventLog.SourceExists(AppName) Then EventLog.CreateEventSource(AppName, "")
        'initialise the log
        Log = New EventLog("Application")
        Log.Source = AppName

    End Sub


    ''' <summary>
    ''' Writes to the log
    ''' </summary>
    ''' <param name="EventText"></param>
    ''' <remarks></remarks>
    Public Shared Sub WriteToLog(ByVal EventText As String, ByVal EntryType As EventLogEntryType)
        Log.WriteEntry(EventText, EntryType)
    End Sub

#End Region




    ''' <summary>
    ''' Loads xEnviro from one or more known locations
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function LoadSettings() As Boolean

        'try the local folder first
        Dim sPath As String = IO.Path.GetDirectoryName(Reflection.Assembly.GetExecutingAssembly.Location)
        sPath += "\" + EnviroFileName

        'try to load
        xEnviro = New XmlDocument
        Try
            xEnviro.Load(sPath)
        Catch ex As Exception
            WriteToLog("Could not load settings from " + sPath + vbCrLf + ex.Message, EventLogEntryType.Error)
            Return False
        End Try

        'get some core settings
        sAwsKey = xEnviro.DocumentElement.GetAttribute("AwsKey")
        sAwsKeySecret = xEnviro.DocumentElement.GetAttribute("AwsSecretKey")

        'save number of disks at the startup
        iDisks = Environment.GetLogicalDrives.Length
        iDisksPhys = WmiDisk.WmiLoadDrives

        'presure it's OK
        Return True

    End Function


    ''' <summary>
    ''' Gets an instance ID from AWS for the current machine -> sInstanceID
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function GetInstanceID() As Boolean

        'do we already have an instance ID?
        If sInstanceID <> "" Then Return True

        Try

            'prepare a new request
            Dim oReq As HttpWebRequest = CType(HttpWebRequest.Create(AwsInstanceMetaUrl + AwsInstanceMetaInstId), HttpWebRequest)

            'get response
            Dim oResp As WebResponse = oReq.GetResponse
            Dim oRespStream As System.IO.Stream = oResp.GetResponseStream
            Dim oSR As New System.IO.StreamReader(oRespStream)

            'save instace ID
            sInstanceID = oSR.ReadToEnd

            'close it off
            oSR.Close()
            oRespStream.Close()
            oResp.Close()

            Return True

        Catch ex As Exception
            Return False
        End Try

        Return False

    End Function

    ''' <summary>
    ''' Get details of this instance
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function GetInstaceDetails() As Boolean

        'do we already have instance details?
        If Not xInstace Is Nothing AndAlso Not xInstace.DocumentElement Is Nothing AndAlso Not oInstance Is Nothing Then Return True

        oInstance = Nothing
        xInstace = Nothing

        'prepare a request
        Dim oClient As New Amazon.EC2.AmazonEC2Client(sAwsKey, sAwsKeySecret)
        Dim oReq As New Amazon.EC2.Model.DescribeInstancesRequest()
        oReq.InstanceId.Add(sInstanceID)

        'try to get the data
        Try
            Dim oResp As Amazon.EC2.Model.DescribeInstancesResponse = oClient.DescribeInstances(oReq)
            xInstace = New XmlDocument
            xInstace.LoadXml(oResp.ToXML)
            oInstance = oResp.DescribeInstancesResult.Reservation(0)
        Catch ex As Exception
            Return False
        End Try

        'got the data OK
        Return True

    End Function


    ''' <summary>
    ''' Gets a list of instances for a security group and returns a list of IPs
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared Sub LoadListOfMachines()

        'do we already have it loaded?
        If Not EnviroBuilder.oInstances Is Nothing Then Return

        'get data
        Dim oClient As New Amazon.EC2.AmazonEC2Client(EnviroBuilder.sAwsKey, EnviroBuilder.sAwsKeySecret)
        Dim oReq As New Amazon.EC2.Model.DescribeInstancesRequest
        Dim oResp As Amazon.EC2.Model.DescribeInstancesResponse = oClient.DescribeInstances(oReq)

        'load as an object and as XML
        EnviroBuilder.oInstances = oResp.DescribeInstancesResult.Reservation
        EnviroBuilder.xInstaces = New XmlDocument
        EnviroBuilder.xInstaces.LoadXml(oResp.ToXML)


    End Sub










End Class
